<?php

declare(strict_types=1);

namespace App\Controller\Publics\V2\Payment;

use App\Constants\Order\OrderCode;
use App\Constants\Payment\PaymentCode;
use App\Controller\Publics\V2\Controllers;
use App\Exception\InternalException;
use App\Factory\Payment\Factory;
use App\Factory\Wechat\Factory as WechatFactory;
use App\Model\Orders\Order;
use App\Resource\Orders\OrderResource;
use App\Services\Payment\Payment;
use EasyWeChat\Pay\Message;
use Hyperf\Collection\Arr;
use Hyperf\DbConnection\Db;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use App\Services\Orders\Order as OrderServices;
use App\Factory\Query\Factory as QueryFactory;
use function Hyperf\Config\config;

/**
 * 回调
 * @package App\Controller\Publics\V2\Payment
 */
class Notify extends Controllers
{
    public function __construct(
        Payment $paymentServices,
        protected OrderServices $orderServices

    )
    {
        parent::__construct();
        $this->services = $paymentServices;
    }

    /**
     * Safepay小程序支付
     * @return ResponseInterface
     * @throws \Throwable
     */
    public function safepayMini():Psr7ResponseInterface
    {
        $input = $this->query();
        $data = $input['data'];
        if (Arr::get($input,'code') !== '0' ||
            !Arr::has($input, 'data') ||
            !Arr::has($input['data'], 'natifyData') ||
            Arr::get($input['data']['natifyData'], 'code') !== '00' ||
            !Arr::get($input['data']['natifyData'], 'data') ||
            Arr::get($input['data']['natifyData']['data'], 'status') !== 'success'
        ) {
            return success();
        }

        $paidNo = $data['trace'];
        /** @var \App\Model\Payment\Payment $model */
        $model = $this->services->with(['order'])->where('no', $paidNo)->firstOrFail();
        if ($model->process_at) {
            return success();
        }

        Db::transaction(function () use ($model, $data) {
            if (!$model->fill(['notify_body'=>$data, 'process_at' => date('Y-m-d H:i:s')])->save() ||
                !$model->order->fill([
                    'paid_at' => date('Y-m-d H:i:s'),
                    'transaction_id' => $data['wxTransactionId'],
                    'pay_status' => OrderCode::PAY_STATUS_PAID,
                    'status' => OrderCode::STATUS_PENDING_DELIVERY
                ])->save()) {
                throw new InternalException();
            }
        });

        return success();
    }

    /**
     * 杉德支付回调
     * @param ResponseInterface $response
     * @param Factory $factory
     * @return Psr7ResponseInterface
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \Throwable
     */
    public function sandpayMini(ResponseInterface $response, Factory $factory):Psr7ResponseInterface
    {
        $input = $this->input();
        $config = config('cloud.payment.sandpayMini');
        $config = array_column($config, null, 'access_mid')[$input['accessMid']];

        $bizData = $factory->sandpayMiniNotify($input, $config);

        $paidNo = $bizData['outOrderNo'];
        /** @var \App\Model\Payment\Payment $model */
        $model = $this->services->with(['order'])->where('no', $paidNo)->firstOrFail();
        if ($model->process_at) {
            return $response->raw('respCode=000000');
        }
        /** @var Order $orderModel */
        $orderModel = $model->order;
        if (in_array(Arr::get($orderModel->queryPaymentOrder(['pay_code' => 'sandpayMini']), 'orderStatus'), [
            'paid', 'success'
        ])) {
            return internalError();
        }

        Db::transaction(function () use ($model, $bizData) {
            if (!$model->fill(['notify_body'=>$bizData, 'process_at' => date('Y-m-d H:i:s')])->save() ||
                !$model->order->fill([
                    'paid_at' => date('Y-m-d H:i:s'),
                    'transaction_id' => $bizData['sandSerialNo'],
                    'pay_status' => OrderCode::PAY_STATUS_PAID,
                    'status' => OrderCode::STATUS_PENDING_DELIVERY
                ])->save()) {
                throw new InternalException();
            }
        });

        return $response->raw('respCode=000000');
    }

    /**
     * Safepay小程序支付
     * @return ResponseInterface
     * @throws \Throwable
     */
    public function neopayMini():Psr7ResponseInterface
    {
        $input = $this->query();;
        if (
            !Arr::has($input, 'code') ||
            Arr::get($input, 'code') !== '00' ||
            !Arr::has($input, 'data') ||
            Arr::get($input['data'], 'status') !== 'success'
        ) {
            return success();
        }

        $paidNo = $input['trace'];
        /** @var \App\Model\Payment\Payment $model */
        $model = $this->services->with(['order'])->where('no', $paidNo)->firstOrFail();
        if ($model->process_at) {
            return success();
        }

        Db::transaction(function () use ($model, $input) {
            if (!$model->fill(['notify_body'=>$input, 'process_at' => date('Y-m-d H:i:s')])->save() ||
                !$model->order->fill([
                    'paid_at' => date('Y-m-d H:i:s'),
                    'transaction_id' => $input['data']['wxTransactionId'],
                    'pay_status' => OrderCode::PAY_STATUS_PAID,
                    'status' => OrderCode::STATUS_PENDING_DELIVERY
                ])->save()) {
                throw new InternalException();
            }
        });

        return success();
    }

    /**
     * 微信小程序支付回调
     * @param int $id
     * @param WechatFactory $factory
     * @return Psr7ResponseInterface
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     * @throws \ReflectionException
     * @throws \Throwable
     */
    public function wechatMini(int $id, WechatFactory $factory): Psr7ResponseInterface
    {
        $config = config("cloud.payment.wechatMini.{$id}");
        $app = $factory->pay($config);

        $server = $app->getServer();
        $server->handlePaid(function (Message $message, \Closure $next) use ($app, $config) {
            $outTradeNo = $message->out_trade_no;

            $app->getValidator()->validate($app->getRequest());

            $response = $app->getClient()
                ->withSerialHeader(array_key_first($config['platform_certs']))
                ->get("v3/pay/transactions/out-trade-no/{$outTradeNo}", [
                    'query' => [
                        'mchid' => $app->getMerchant()->getMerchantId()
                    ]
                ]);

            $orderInfo = $response->toArray();
            /** @var \App\Model\Payment\Payment $model */
            $model = $this->services
                ->where('no', $outTradeNo)
                ->where('type', 1)
                ->where('code', 'wechatMini')
                ->firstOrFail();
            if ($model->process_at) {
                return $next($message);
            }
            $amount = bcdiv((string)$orderInfo['amount']['payer_total'], '100', 2);
            if (bccomp($model->amount, $amount, 2) !== 0) {
                logs("支付金额不一致: [{$amount}, {$model->amount}]");
                throw new InternalException();
            }

            Db::transaction(function () use ($model, $message) {
                if (!$model->fill(['notify_body'=>$message->toArray(), 'process_at' => date('Y-m-d H:i:s')])->save() ||
                    !$model->order->fill([
                        'paid_at' => date('Y-m-d H:i:s'),
                        'transaction_id' => $message->transaction_id,
                        'pay_status' => OrderCode::PAY_STATUS_PAID,
                        'status' => OrderCode::STATUS_PENDING_DELIVERY
                    ])->save()) {
                    throw new InternalException();
                }
            });

            return $next($message);
        });

        return $server->serve();
    }

    /**
     * 统统付回调通知
     * @param int $id
     * @param Factory $factory
     * @param ResponseInterface $response
     * @return Psr7ResponseInterface
     */
    public function sumpayMini(int $id, Factory $factory, ResponseInterface $response, QueryFactory $queryFactory): Psr7ResponseInterface
    {
        $config = config("cloud.payment.sumpayMini.{$id}");
        $input = $this->input();

        try {
            $data = $factory->sumpayMiniNotify($input, $config);

            if (Arr::get($data, 'status') != 1) {
                return $response->json([
                    'resp_code' => '000001',
                    'resp_msg' => '支付未成功'
                ]);
            }

            $paidNo = $input['order_no'];
            /** @var \App\Model\Payment\Payment $model */
            $model = $this->services->with(['order'])->where('no', $paidNo)->firstOrFail();
            if ($model->process_at) {
                return $response->json([
                    'resp_code' => '000000',
                    'resp_msg' => ''
                ]);
            }

            $params = (new OrderResource($model->order))->toQueryArray();
            [,, $queryInfo] = $queryFactory->sumpayMini($params, $config);

            Db::transaction(function () use ($model, $input, $queryInfo) {
                if (!$model->fill(['notify_body'=>$input, 'process_at' => date('Y-m-d H:i:s')])->save() ||
                    !$model->order->fill([
                        'paid_at' => date('Y-m-d H:i:s'),
                        'transaction_id' => Arr::get($input, 'trade_no'),
                        'pay_status' => OrderCode::PAY_STATUS_PAID,
                        'status' => OrderCode::STATUS_PENDING_DELIVERY
                    ])->save()) {
                    throw new InternalException();
                }
            });
        } catch (\Throwable $throwable) {
            logs($throwable);
            return $response->json([
                'resp_code' => '000002',
                'resp_msg' => $throwable->getMessage()
            ]);
        }

        return $response->json([
            'resp_code' => '000000',
            'resp_msg' => ''
        ]);
    }

    /**
     * 汇付支付回调通知
     * @param int $id
     * @param Factory $factory
     * @param ResponseInterface $response
     * @return Psr7ResponseInterface
     */
    public function chinapnrpayMini(int $id, Factory $factory, ResponseInterface $response): Psr7ResponseInterface
    {
        $config = config("cloud.payment.chinapnrpayMini.{$id}");
        $input = $this->input();
        try {
            $data = $factory->chinapnrpayMiniNotify($input, $config);

            if (Arr::get($data, 'errCode') !== '000000' || Arr::get($data, 'payResult') !== '10') {
                return $response->raw('FAILED');
            }

            $paidNo = Arr::get($input, 'orderId');
            /** @var \App\Model\Payment\Payment $model */
            $model = $this->services->with(['order'])->where('no', $paidNo)->firstOrFail();
            if ($model->process_at) {
                return $response->raw('SUCCESS');
            }

            Db::transaction(function () use ($model, $input) {
                $extInfo = json_decode(urldecode(Arr::get($input, 'extInfo')), true);
                if (!$model->fill(['notify_body'=>$input, 'process_at' => date('Y-m-d H:i:s')])->save() ||
                    !$model->order->fill([
                        'paid_no' => Arr::get($input, 'dealId'),
                        'paid_at' => date('Y-m-d H:i:s'),
                        'transaction_id' => Arr::get($extInfo, 'authTransId'),
                        'pay_status' => OrderCode::PAY_STATUS_PAID,
                        'status' => OrderCode::STATUS_PENDING_DELIVERY
                    ])->save()) {
                    throw new InternalException();
                }
            });
        } catch (\Throwable $throwable) {
            logs($throwable);
            return $response->raw('FAILED');
        }

        return $response->raw('SUCCESS');
    }
}
